use pest::Parser;
use pest_derive::Parser;
use std::env;
use std::fs;
use std::process;

#[derive(Parser)]
#[grammar = "sysy.pest"]
pub struct SysYParser;

fn main() {
    let args: Vec<String> = env::args().collect();
    if args.len() != 2 {
        eprintln!("Usage: {} <filename>", args[0]);
        process::exit(1);
    }

    let filename = &args[1];
    let input = match fs::read_to_string(filename) {
        Ok(content) => content,
        Err(err) => {
            eprintln!("Error reading file {}: {}", filename, err);
            process::exit(1);
        }
    };

    analyze_lexical(&input);
}

fn analyze_lexical(input: &str) {
    // First, remove all comments from the input
    let cleaned_input = remove_comments(input);

    let lines: Vec<&str> = cleaned_input.lines().collect();
    let mut errors = Vec::new();
    let mut tokens = Vec::new();

    for (line_idx, line) in lines.iter().enumerate() {
        let line_num = line_idx + 1;
        let mut pos = 0;

        while pos < line.len() {
            let remaining = &line[pos..];

            // Skip whitespace
            if remaining.chars().next().unwrap().is_whitespace() {
                pos += 1;
                continue;
            }

            // Try to parse a token
            match parse_next_token(remaining) {
                Ok((token_type, token_text, length)) => {
                    tokens.push((token_type, token_text, line_num));
                    pos += length;
                }
                Err(ch) => {
                    errors.push(format!(
                        "Error type A at Line {}: Mysterious character \"{}\"",
                        line_num, ch
                    ));
                    break;
                }
            }
        }

        // If we found an error, break out of the line loop too
        if !errors.is_empty() {
            break;
        }
    }

    // Output results
    if !errors.is_empty() {
        for error in errors {
            eprintln!("{}.", error);
        }
    } else {
        for (token_type, token_text, line_num) in tokens {
            eprintln!("{} {} at Line {}.", token_type, token_text, line_num);
        }
    }
}

fn remove_comments(input: &str) -> String {
    let mut result = String::new();
    let mut chars = input.chars().peekable();
    let mut line_num = 1;

    while let Some(ch) = chars.next() {
        if ch == '/' {
            if let Some(&next_ch) = chars.peek() {
                if next_ch == '/' {
                    // Line comment - skip until newline
                    chars.next(); // consume the second '/'
                    while let Some(ch) = chars.next() {
                        if ch == '\n' {
                            result.push('\n');
                            line_num += 1;
                            break;
                        }
                    }
                } else if next_ch == '*' {
                    // Block comment - skip until */
                    chars.next(); // consume the '*'
                    let mut found_end = false;

                    while let Some(ch) = chars.next() {
                        if ch == '\n' {
                            result.push('\n');
                            line_num += 1;
                        } else if ch == '*' {
                            if let Some(&next_ch) = chars.peek() {
                                if next_ch == '/' {
                                    chars.next(); // consume the '/'
                                    found_end = true;
                                    break;
                                }
                            }
                        }
                    }
                } else {
                    result.push(ch);
                }
            } else {
                result.push(ch);
            }
        } else {
            result.push(ch);
            if ch == '\n' {
                line_num += 1;
            }
        }
    }

    result
}

fn parse_next_token(input: &str) -> Result<(String, String, usize), char> {
    if input.is_empty() {
        return Err(' ');
    }

    let first_char = input.chars().next().unwrap();

    // Try keywords first
    for &(keyword, token_type) in &[
        ("int", "INT"),
        ("return", "RETURN"),
        ("if", "IF"),
        ("else", "ELSE"),
        ("while", "WHILE"),
        ("break", "BREAK"),
        ("continue", "CONTINUE"),
        ("const", "CONST"),
        ("void", "VOID"),
    ] {
        if input.starts_with(keyword) {
            let next_char_pos = keyword.len();
            if next_char_pos >= input.len()
                || (!input.chars().nth(next_char_pos).unwrap().is_alphanumeric()
                    && input.chars().nth(next_char_pos).unwrap() != '_')
            {
                return Ok((token_type.to_string(), keyword.to_string(), keyword.len()));
            }
        }
    }

    // Try operators (order matters for multi-char operators)
    for &(op, token_type) in &[
        ("==", "EQ"),
        ("!=", "NE"),
        ("<=", "LE"),
        (">=", "GE"),
        ("&&", "AND"),
        ("||", "OR"),
        ("=", "ASSIGN"),
        ("<", "LT"),
        (">", "GT"),
        ("!", "NOT"),
        ("+", "PLUS"),
        ("-", "MINUS"),
        ("*", "MUL"),
        ("/", "DIV"),
        ("%", "MOD"),
        ("(", "L_PAREN"),
        (")", "R_PAREN"),
        ("{", "L_BRACE"),
        ("}", "R_BRACE"),
        ("[", "L_BRACKT"),
        ("]", "R_BRACKT"),
        (";", "SEMICOLON"),
        (",", "COMMA"),
    ] {
        if input.starts_with(op) {
            return Ok((token_type.to_string(), op.to_string(), op.len()));
        }
    }

    // Try integer constants
    if first_char.is_ascii_digit() {
        return parse_integer(input);
    }

    // Try identifiers
    if first_char.is_ascii_alphabetic() || first_char == '_' {
        return parse_identifier(input);
    }

    Err(first_char)
}

fn parse_integer(input: &str) -> Result<(String, String, usize), char> {
    let mut pos = 0;
    let chars: Vec<char> = input.chars().collect();

    if chars.is_empty() {
        return Err(' ');
    }

    // Hexadecimal
    if chars.len() > 2 && chars[0] == '0' && (chars[1] == 'x' || chars[1] == 'X') {
        pos = 2;
        while pos < chars.len() && chars[pos].is_ascii_hexdigit() {
            pos += 1;
        }
        if pos > 2 {
            let hex_str = &input[2..pos];
            let decimal_value = i64::from_str_radix(hex_str, 16).unwrap_or(0);
            return Ok(("INTEGER_CONST".to_string(), decimal_value.to_string(), pos));
        }
    }

    // Octal (starts with 0 and has octal digits)
    if chars[0] == '0' && chars.len() > 1 && chars[1].is_ascii_digit() {
        pos = 1;
        while pos < chars.len() && chars[pos] >= '0' && chars[pos] <= '7' {
            pos += 1;
        }
        let octal_str = &input[0..pos];
        let decimal_value = i64::from_str_radix(octal_str, 8).unwrap_or(0);
        return Ok(("INTEGER_CONST".to_string(), decimal_value.to_string(), pos));
    }

    // Decimal
    while pos < chars.len() && chars[pos].is_ascii_digit() {
        pos += 1;
    }

    if pos > 0 {
        let decimal_value: i64 = input[0..pos].parse().unwrap_or(0);
        Ok(("INTEGER_CONST".to_string(), decimal_value.to_string(), pos))
    } else {
        Err(chars[0])
    }
}

fn parse_identifier(input: &str) -> Result<(String, String, usize), char> {
    let mut pos = 0;
    let chars: Vec<char> = input.chars().collect();

    if chars.is_empty() || !(chars[0].is_ascii_alphabetic() || chars[0] == '_') {
        return Err(chars[0]);
    }

    while pos < chars.len() && (chars[pos].is_ascii_alphanumeric() || chars[pos] == '_') {
        pos += 1;
    }

    let ident_str = &input[0..pos];
    Ok(("IDENT".to_string(), ident_str.to_string(), pos))
}
